{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import kotlinx.serialization.*\n",
    "import kotlinx.serialization.json.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "@Serializable\n",
    "data class Pt(val x: Int, val y: Int)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"x\":2,\"y\":3}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Json.encodeToString(Pt(2, 3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "var pt: Pt? = null\n",
    "notebook.commManager.registerCommTarget(\"t_clicker\") { comm, openData ->    \n",
    "    comm.onData<Pt> { msgPt ->\n",
    "        // Thread.sleep(3000)\n",
    "        pt = msgPt \n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div style=\"background-color:#AAAA00; width:200px; height: 200px;\" id=\"clicker\"></div>\n",
       "<div style=\"font-size:30px; margin-top:5px;\" id=\"clickerOut\"></div>\n",
       "<script>\n",
       "    (function() {\n",
       "        const dOut = document.getElementById(\"clickerOut\");\n",
       "        const d = document.getElementById(\"clicker\");\n",
       "        \n",
       "        const comm = Jupyter.notebook.kernel.comm_manager.new_comm(\"t_clicker\", {});            \n",
       "        d.addEventListener(\"click\", (event) => {\n",
       "            dOut.innerHTML = \"x = \" + event.offsetX + \", y = \" + event.offsetY;\n",
       "            comm.send({x: event.offsetX, y: event.offsetY});\n",
       "        });\n",
       "    })();\n",
       "</script>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "HTML(\n",
    "    \"\"\"\n",
    "    <div style=\"background-color:#AAAA00; width:200px; height: 200px;\" id=\"clicker\"></div>\n",
    "    <div style=\"font-size:30px; margin-top:5px;\" id=\"clickerOut\"></div>\n",
    "    <script>\n",
    "        (function() {\n",
    "            const dOut = document.getElementById(\"clickerOut\");\n",
    "            const d = document.getElementById(\"clicker\");\n",
    "            \n",
    "            const comm = Jupyter.notebook.kernel.comm_manager.new_comm(\"t_clicker\", {});            \n",
    "            d.addEventListener(\"click\", (event) => {\n",
    "                dOut.innerHTML = \"x = \" + event.offsetX + \", y = \" + event.offsetY;\n",
    "                comm.send({x: event.offsetX, y: event.offsetY});\n",
    "            });\n",
    "        })();\n",
    "    </script>\n",
    "    \"\"\".trimIndent()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Pt(x=144, y=101)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "interface Counter {\n",
    "    fun inc()\n",
    "    fun dec()\n",
    "    val value: Int\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Counter1(private var cnt: Int = 0): Counter {\n",
    "    override fun inc() { ++cnt }\n",
    "    override fun dec() { --cnt }\n",
    "    \n",
    "    override val value: Int get() = cnt\n",
    "}\n",
    "\n",
    "USE { \n",
    "    render<Counter1> { \n",
    "        HTML(\"\"\"\n",
    "            <button id=\"inc\">+</button>\n",
    "            <span id=\"cntVal\">${it.value}</span>\n",
    "            <button id=\"dec\">-</button>\n",
    "            \n",
    "            <script>\n",
    "                (function(){\n",
    "                    function getVal() {\n",
    "                        return parseInt(document.getElementById(\"cntVal\").innerHTML);\n",
    "                    }\n",
    "                    function setVal(v) {\n",
    "                        document.getElementById(\"cntVal\").innerHTML = v + \"\";\n",
    "                    }\n",
    "                    \n",
    "                    const incButton = document.getElementById(\"inc\");\n",
    "                    const decButton = document.getElementById(\"dec\");\n",
    "                    \n",
    "                    incButton.addEventListener(\"click\", (e) => {\n",
    "                        setVal(getVal() + 1);\n",
    "                    });\n",
    "                    decButton.addEventListener(\"click\", (e) => {\n",
    "                        setVal(getVal() - 1);\n",
    "                    });\n",
    "                })();\n",
    "            </script>\n",
    "        \"\"\".trimIndent())\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<button id=\"inc\">+</button>\n",
       "<span id=\"cntVal\">0</span>\n",
       "<button id=\"dec\">-</button>\n",
       "\n",
       "<script>\n",
       "    (function(){\n",
       "        function getVal() {\n",
       "            return parseInt(document.getElementById(\"cntVal\").innerHTML);\n",
       "        }\n",
       "        function setVal(v) {\n",
       "            document.getElementById(\"cntVal\").innerHTML = v + \"\";\n",
       "        }\n",
       "        \n",
       "        const incButton = document.getElementById(\"inc\");\n",
       "        const decButton = document.getElementById(\"dec\");\n",
       "        \n",
       "        incButton.addEventListener(\"click\", (e) => {\n",
       "            setVal(getVal() + 1);\n",
       "        });\n",
       "        decButton.addEventListener(\"click\", (e) => {\n",
       "            setVal(getVal() - 1);\n",
       "        });\n",
       "    })();\n",
       "</script>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "val cnt1 = Counter1()\n",
    "cnt1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cnt1.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import kotlinx.serialization.*\n",
    "import kotlinx.serialization.json.*\n",
    "\n",
    "@Serializable\n",
    "class CounterOpen(val id: String)\n",
    "\n",
    "@Serializable\n",
    "class CounterCommand(val command: String)\n",
    "\n",
    "@Serializable\n",
    "class CounterValueUpdate(val value: Int)\n",
    "\n",
    "\n",
    "object CounterFactory {\n",
    "    private val idToCtr = mutableMapOf<String, CounterImpl>()\n",
    "    private val idToComm = mutableMapOf<String, Comm>()\n",
    "    \n",
    "    fun create(): Counter {\n",
    "        val ctr = CounterImpl()\n",
    "        idToCtr[ctr.id] = ctr\n",
    "        return ctr\n",
    "    }\n",
    "    \n",
    "    fun addComm(id: String, comm: Comm) {\n",
    "        idToComm[id] = comm\n",
    "    }\n",
    "    \n",
    "    fun getCounter(id: String): Counter = idToCtr[id]!!\n",
    "    \n",
    "    fun updateValue(id: String, value: Int) {\n",
    "        val comm = idToComm[id] ?: return\n",
    "        comm.sendData(CounterValueUpdate(value))\n",
    "    }\n",
    "    \n",
    "    private class CounterImpl(\n",
    "        private var cnt: Int = 0,\n",
    "        val id: String = java.util.UUID.randomUUID().toString()\n",
    "    ): Counter, Renderable {\n",
    "        override fun inc() {\n",
    "            ++cnt\n",
    "            CounterFactory.updateValue(id, value)\n",
    "        }\n",
    "        override fun dec() {\n",
    "            --cnt\n",
    "            CounterFactory.updateValue(id, value)\n",
    "        }\n",
    "\n",
    "        override val value: Int get() = cnt \n",
    "        \n",
    "        override fun render(notebook: Notebook): DisplayResult {\n",
    "            return HTML(\"\"\"\n",
    "                <button id=\"inc\" onclick=\"incSend('$id')\">+</button>\n",
    "                <span class=\"cntVal$id\">$value</span>\n",
    "                <button id=\"dec\" onclick=\"decSend('$id')\">-</button>\n",
    "\n",
    "                <script>\n",
    "                    window.incSend = function(id) {\n",
    "                        cntComms[id].send({command : \"inc\"});\n",
    "                    };\n",
    "                    window.decSend = function(id) {\n",
    "                        cntComms[id].send({command : \"dec\"});\n",
    "                    };\n",
    "\n",
    "                    (function(){\n",
    "                        const cntId = \"$id\";\n",
    "                        const comm = Jupyter.notebook.kernel.comm_manager.new_comm(\"t_counter\", { id: cntId });\n",
    "                        window.cntComms = window.cntComms || {};\n",
    "                        window.cntComms[cntId] = comm;\n",
    "\n",
    "                        function setVal(id, v) {\n",
    "                            const outs = document.getElementsByClassName(\"cntVal\" + id);\n",
    "                            const contents = v + \"\";\n",
    "                            for (out of outs) {\n",
    "                                out.innerHTML = contents;\n",
    "                            }\n",
    "                        }\n",
    "\n",
    "                        comm.on_msg((msg) => {\n",
    "                            const value = msg.content.data.value;\n",
    "                            setVal(cntId, value);\n",
    "                        });\n",
    "                    })();\n",
    "                </script>\n",
    "            \"\"\".trimIndent())\n",
    "        }\n",
    "    }    \n",
    "}\n",
    "\n",
    "\n",
    "\n",
    "notebook.commManager.registerCommTarget(\"t_counter\") { comm, openData ->\n",
    "    val counterId = Json.decodeFromJsonElement<CounterOpen>(openData).id\n",
    "    CounterFactory.addComm(counterId, comm)\n",
    "\n",
    "    val counter = CounterFactory.getCounter(counterId)\n",
    "    \n",
    "    comm.onData<CounterCommand> { d ->\n",
    "        val command = d.command\n",
    "        when(command) {\n",
    "            \"inc\" -> counter.inc()\n",
    "            \"dec\" -> counter.dec()\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "val ctr2 = CounterFactory.create()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<button id=\"inc\" onclick=\"incSend('569b889e-32b9-4fa1-a75a-0d03bdbfa906')\">+</button>\n",
       "<span class=\"cntVal569b889e-32b9-4fa1-a75a-0d03bdbfa906\">0</span>\n",
       "<button id=\"dec\" onclick=\"decSend('569b889e-32b9-4fa1-a75a-0d03bdbfa906')\">-</button>\n",
       "\n",
       "<script>\n",
       "    window.incSend = function(id) {\n",
       "        cntComms[id].send({command : \"inc\"});\n",
       "    };\n",
       "    window.decSend = function(id) {\n",
       "        cntComms[id].send({command : \"dec\"});\n",
       "    };\n",
       "\n",
       "    (function(){\n",
       "        const cntId = \"569b889e-32b9-4fa1-a75a-0d03bdbfa906\";\n",
       "        const comm = Jupyter.notebook.kernel.comm_manager.new_comm(\"t_counter\", { id: cntId });\n",
       "        window.cntComms = window.cntComms || {};\n",
       "        window.cntComms[cntId] = comm;\n",
       "\n",
       "        function setVal(id, v) {\n",
       "            const outs = document.getElementsByClassName(\"cntVal\" + id);\n",
       "            const contents = v + \"\";\n",
       "            for (out of outs) {\n",
       "                out.innerHTML = contents;\n",
       "            }\n",
       "        }\n",
       "\n",
       "        comm.on_msg((msg) => {\n",
       "            const value = msg.content.data.value;\n",
       "            setVal(cntId, value);\n",
       "        });\n",
       "    })();\n",
       "</script>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ctr2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ctr2.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "ctr2.inc()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "for (i in 1..10) {\n",
    "    ctr2.inc()\n",
    "    Thread.sleep(500)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "val ctr3 = CounterFactory.create()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<button id=\"inc\" onclick=\"incSend('fb5a0021-ddc9-4bad-90ed-60e7ea37a2c4')\">+</button>\n",
       "<span class=\"cntValfb5a0021-ddc9-4bad-90ed-60e7ea37a2c4\">0</span>\n",
       "<button id=\"dec\" onclick=\"decSend('fb5a0021-ddc9-4bad-90ed-60e7ea37a2c4')\">-</button>\n",
       "\n",
       "<script>\n",
       "    window.incSend = function(id) {\n",
       "        cntComms[id].send({command : \"inc\"});\n",
       "    };\n",
       "    window.decSend = function(id) {\n",
       "        cntComms[id].send({command : \"dec\"});\n",
       "    };\n",
       "\n",
       "    (function(){\n",
       "        const cntId = \"fb5a0021-ddc9-4bad-90ed-60e7ea37a2c4\";\n",
       "        const comm = Jupyter.notebook.kernel.comm_manager.new_comm(\"t_counter\", { id: cntId });\n",
       "        window.cntComms = window.cntComms || {};\n",
       "        window.cntComms[cntId] = comm;\n",
       "\n",
       "        function setVal(id, v) {\n",
       "            const outs = document.getElementsByClassName(\"cntVal\" + id);\n",
       "            const contents = v + \"\";\n",
       "            for (out of outs) {\n",
       "                out.innerHTML = contents;\n",
       "            }\n",
       "        }\n",
       "\n",
       "        comm.on_msg((msg) => {\n",
       "            const value = msg.content.data.value;\n",
       "            setVal(cntId, value);\n",
       "        });\n",
       "    })();\n",
       "</script>"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ctr3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<button id=\"inc\" onclick=\"incSend('fb5a0021-ddc9-4bad-90ed-60e7ea37a2c4')\">+</button>\n",
       "<span class=\"cntValfb5a0021-ddc9-4bad-90ed-60e7ea37a2c4\">0</span>\n",
       "<button id=\"dec\" onclick=\"decSend('fb5a0021-ddc9-4bad-90ed-60e7ea37a2c4')\">-</button>\n",
       "\n",
       "<script>\n",
       "    window.incSend = function(id) {\n",
       "        cntComms[id].send({command : \"inc\"});\n",
       "    };\n",
       "    window.decSend = function(id) {\n",
       "        cntComms[id].send({command : \"dec\"});\n",
       "    };\n",
       "\n",
       "    (function(){\n",
       "        const cntId = \"fb5a0021-ddc9-4bad-90ed-60e7ea37a2c4\";\n",
       "        const comm = Jupyter.notebook.kernel.comm_manager.new_comm(\"t_counter\", { id: cntId });\n",
       "        window.cntComms = window.cntComms || {};\n",
       "        window.cntComms[cntId] = comm;\n",
       "\n",
       "        function setVal(id, v) {\n",
       "            const outs = document.getElementsByClassName(\"cntVal\" + id);\n",
       "            const contents = v + \"\";\n",
       "            for (out of outs) {\n",
       "                out.innerHTML = contents;\n",
       "            }\n",
       "        }\n",
       "\n",
       "        comm.on_msg((msg) => {\n",
       "            const value = msg.content.data.value;\n",
       "            setVal(cntId, value);\n",
       "        });\n",
       "    })();\n",
       "</script>"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ctr3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "ctr3.inc()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import kotlinx.serialization.*\n",
    "import kotlinx.serialization.json.*\n",
    "\n",
    "interface WidgetConstrictorArg\n",
    "\n",
    "interface WidgetState {\n",
    "    val json: JsonObject\n",
    "}\n",
    "\n",
    "interface WidgetCommand\n",
    "\n",
    "interface Widget<S: WidgetState, C: WidgetCommand> {\n",
    "    val id: String\n",
    "    val state: S\n",
    "    var comm: Comm? \n",
    "    \n",
    "    // call it after every kernel-side update\n",
    "    fun syncState() {\n",
    "        comm?.send(state.json)\n",
    "    }\n",
    "    \n",
    "    fun updateState(command: C)\n",
    "    \n",
    "    // Generally HTML code. May call JS function kotlinCommSend(target, id, command)\n",
    "    // `command` will be deserialized to type C\n",
    "    fun renderState(targetName: String): String\n",
    "    \n",
    "    // arguments are `elem` and `state`. `elem` is a div container where all generated with renderState() is held.\n",
    "    fun setStateJs(): String\n",
    "}\n",
    "\n",
    "abstract class WidgetFactory<A: WidgetConstrictorArg, S: WidgetState, C: WidgetCommand, W: Widget<S, C>>(\n",
    "    private val targetName: String,\n",
    "    widgetClass: kotlin.reflect.KClass<W>,\n",
    "    notebook: Notebook\n",
    ") {\n",
    "    protected abstract fun createImpl(args: A): W\n",
    "    protected abstract fun deserializeCommand(json: JsonObject): C\n",
    "    \n",
    "    private val idToWidget = mutableMapOf<String, W>()\n",
    "    \n",
    "    init {\n",
    "        notebook.commManager.registerCommTarget(targetName) { comm, openData ->\n",
    "            val widgetId = openData[\"id\"]!!.jsonPrimitive.content\n",
    "            addComm(widgetId, comm)\n",
    "\n",
    "            val widget = getWidget(widgetId)\n",
    "\n",
    "            comm.onMessage { d ->\n",
    "                val command = deserializeCommand(d)\n",
    "                widget.updateState(command)\n",
    "            }\n",
    "            \n",
    "            widget.syncState()\n",
    "        }\n",
    "        \n",
    "        notebook.renderersProcessor.registerWithoutOptimizing(createRenderer(widgetClass) { w ->\n",
    "            HTML(\n",
    "                \"\"\"\n",
    "                <div class=\"widget_${targetName}_${w.id}\">${w.renderState(targetName)}</div>\n",
    "\n",
    "                <script>\n",
    "                    window.kotlinCommSend = function(target, wId, command) {\n",
    "                        window.kotlinComms[target][wId].send(command);\n",
    "                    };\n",
    "                    \n",
    "                    window.setElementState = function(elem, state) {\n",
    "                        ${w.setStateJs()}\n",
    "                    };\n",
    "\n",
    "                    window.setState = function(target, id, data) {\n",
    "                        const outs = document.getElementsByClassName(\"widget_\" + target + \"_\" + id);\n",
    "                        for (out of outs) {\n",
    "                            setElementState(out, data);\n",
    "                        }\n",
    "                    };\n",
    "                    \n",
    "                    (function(){\n",
    "                        const initComm = function() {\n",
    "                            const wId = \"${w.id}\";\n",
    "                            const target = \"$targetName\";\n",
    "                            const comm = Jupyter.notebook.kernel.comm_manager.new_comm(target, { id: wId });\n",
    "                            window.kotlinComms = window.kotlinComms || {};\n",
    "                            window.kotlinComms[target] = window.kotlinComms[target] || {};\n",
    "                            window.kotlinComms[target][wId] = comm;\n",
    "\n",
    "                            comm.on_msg((msg) => {\n",
    "                                const d = msg.content.data;\n",
    "                                setState(target, wId, d);\n",
    "                            });\n",
    "                        };\n",
    "\n",
    "                        if (Jupyter.notebook.kernel != null) initComm();\n",
    "                        else window.onload = initComm;\n",
    "                    })();\n",
    "                </script>\n",
    "                \"\"\".trimIndent()\n",
    "            )\n",
    "        })\n",
    "    }\n",
    "    \n",
    "    \n",
    "    fun create(args: A): W {\n",
    "        return createImpl(args).also { w -> idToWidget[w.id] = w }\n",
    "    }\n",
    "    \n",
    "    fun addComm(id: String, comm: Comm) {\n",
    "        val widget = idToWidget[id] ?: return\n",
    "        widget.comm = comm\n",
    "    }\n",
    "    \n",
    "    fun getWidget(id: String): W = idToWidget[id]!!\n",
    "    \n",
    "    \n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class ListWidgetArgs(\n",
    "    val list: List<Int>,\n",
    "    val shownElementsLimit: Int,\n",
    "): WidgetConstrictorArg\n",
    "\n",
    "@Serializable\n",
    "class ListWidgetState(\n",
    "    val d: String\n",
    ") : WidgetState {\n",
    "    override val json get() = Json.encodeToJsonElement(this).jsonObject\n",
    "}\n",
    "\n",
    "@Serializable\n",
    "class ListWidgetCommand : WidgetCommand\n",
    "\n",
    "class ListWidget(\n",
    "    override val id: String,\n",
    "    val data: List<Int>,\n",
    "    private var shownElementsLimit: Int,\n",
    "): Widget<ListWidgetState, ListWidgetCommand> {\n",
    "    override val state get() = ListWidgetState(data.take(minOf(shownElementsLimit, data.size)).toString())\n",
    "    override var comm: Comm? = null\n",
    "    \n",
    "    override fun updateState(command: ListWidgetCommand) {\n",
    "        showMore()\n",
    "    }\n",
    "    \n",
    "    fun showMore() {\n",
    "        shownElementsLimit += 10\n",
    "        syncState()\n",
    "    }\n",
    "    \n",
    "    override fun renderState(targetName: String): String {\n",
    "        return \"\"\"\n",
    "            <button onclick=\"kotlinCommSend('${targetName}', '${id}', {})\">Show more</button>\n",
    "            <span class=\"listContent\">${state.d}</span>\n",
    "        \"\"\"\n",
    "    }\n",
    "    \n",
    "    override fun setStateJs(): String {\n",
    "        return \"\"\"\n",
    "            const contentElem = elem.getElementsByClassName('listContent')[0];\n",
    "            contentElem.innerHTML = state.d;\n",
    "        \"\"\"\n",
    "    }\n",
    "    \n",
    "}\n",
    "\n",
    "class ListWidgetFactory(notebook: Notebook): WidgetFactory<ListWidgetArgs, ListWidgetState, ListWidgetCommand, ListWidget>(\"t_list\", ListWidget::class, notebook) {\n",
    "    override fun createImpl(args: ListWidgetArgs): ListWidget {\n",
    "        return ListWidget(java.util.UUID.randomUUID().toString(), args.list, args.shownElementsLimit)\n",
    "    }\n",
    "    override fun deserializeCommand(json: JsonObject): ListWidgetCommand {\n",
    "        return Json.decodeFromJsonElement<ListWidgetCommand>(json)\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "val listFactory = ListWidgetFactory(notebook)\n",
    "val listWidget = listFactory.create(ListWidgetArgs((1..1000).toList(), 5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "        <div class=\"widget_t_list_f0b71a14-2181-4485-b6f5-c3a80495e996\">\n",
       "    <button onclick=\"kotlinCommSend('t_list', 'f0b71a14-2181-4485-b6f5-c3a80495e996', {})\">Show more</button>\n",
       "    <span class=\"listContent\">[1, 2, 3, 4, 5]</span>\n",
       "</div>\n",
       "\n",
       "        <script>\n",
       "            window.kotlinCommSend = function(target, wId, command) {\n",
       "                window.kotlinComms[target][wId].send(command);\n",
       "            };\n",
       "            \n",
       "            window.setElementState = function(elem, state) {\n",
       "                \n",
       "    const contentElem = elem.getElementsByClassName('listContent')[0];\n",
       "    contentElem.innerHTML = state.d;\n",
       "\n",
       "            };\n",
       "\n",
       "            window.setState = function(target, id, data) {\n",
       "                const outs = document.getElementsByClassName(\"widget_\" + target + \"_\" + id);\n",
       "                for (out of outs) {\n",
       "                    setElementState(out, data);\n",
       "                }\n",
       "            };\n",
       "            \n",
       "            (function(){\n",
       "                const initComm = function() {\n",
       "                    const wId = \"f0b71a14-2181-4485-b6f5-c3a80495e996\";\n",
       "                    const target = \"t_list\";\n",
       "                    const comm = Jupyter.notebook.kernel.comm_manager.new_comm(target, { id: wId });\n",
       "                    window.kotlinComms = window.kotlinComms || {};\n",
       "                    window.kotlinComms[target] = window.kotlinComms[target] || {};\n",
       "                    window.kotlinComms[target][wId] = comm;\n",
       "\n",
       "                    comm.on_msg((msg) => {\n",
       "                        const d = msg.content.data;\n",
       "                        setState(target, wId, d);\n",
       "                    });\n",
       "                };\n",
       "\n",
       "                if (Jupyter.notebook.kernel != null) initComm();\n",
       "                else window.onload = initComm;\n",
       "            })();\n",
       "        </script>"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "listWidget"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "listWidget.showMore()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "listWidget"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class ListWidgetArgs(\n",
    "    val list: List<Int>,\n",
    "    val shownElementsLimit: Int,\n",
    "): WidgetConstrictorArg\n",
    "\n",
    "@Serializable\n",
    "class ListWidgetState(\n",
    "    val d: String\n",
    ") : WidgetState {\n",
    "    override val json get() = Json.encodeToJsonElement(this).jsonObject\n",
    "}\n",
    "\n",
    "@Serializable\n",
    "class ListWidgetCommand : WidgetCommand\n",
    "\n",
    "class ListWidget(\n",
    "    override val id: String,\n",
    "    val data: MutableList<Int>,\n",
    "    private var shownElementsLimit: Int,\n",
    "): Widget<ListWidgetState, ListWidgetCommand> {\n",
    "    override val state get() = ListWidgetState(data.take(minOf(shownElementsLimit, data.size)).toString())\n",
    "    override var comm: Comm? = null\n",
    "    \n",
    "    override fun updateState(command: ListWidgetCommand) {\n",
    "        showMore()\n",
    "    }\n",
    "    \n",
    "    fun showMore() {\n",
    "        shownElementsLimit += 10\n",
    "        syncState()\n",
    "    }\n",
    "    \n",
    "    operator fun set(i: Int, v: Int) {\n",
    "        data[i] = v\n",
    "        syncState()\n",
    "    }\n",
    "    \n",
    "    override fun renderState(targetName: String): String {\n",
    "        return \"\"\"\n",
    "            <button onclick=\"kotlinCommSend('${targetName}', '${id}', {})\">Show more</button>\n",
    "            <span class=\"listContent\">${state.d}</span>\n",
    "        \"\"\"\n",
    "    }\n",
    "    \n",
    "    override fun setStateJs(): String {\n",
    "        return \"\"\"\n",
    "            const contentElem = elem.getElementsByClassName('listContent')[0];\n",
    "            contentElem.innerHTML = state.d;\n",
    "        \"\"\"\n",
    "    }\n",
    "    \n",
    "}\n",
    "\n",
    "class ListWidgetFactory(notebook: Notebook): WidgetFactory<ListWidgetArgs, ListWidgetState, ListWidgetCommand, ListWidget>(\"t_list2\", ListWidget::class, notebook) {\n",
    "    override fun createImpl(args: ListWidgetArgs): ListWidget {\n",
    "        return ListWidget(java.util.UUID.randomUUID().toString(), args.list.toMutableList(), args.shownElementsLimit)\n",
    "    }\n",
    "    override fun deserializeCommand(json: JsonObject): ListWidgetCommand {\n",
    "        return Json.decodeFromJsonElement<ListWidgetCommand>(json)\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "val listFactory = ListWidgetFactory(notebook)\n",
    "val listWidget = listFactory.create(ListWidgetArgs((1..1000).toList(), 5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "        <div class=\"widget_t_list2_95d0010a-7d45-488b-bdcc-9b528e11254a\">\n",
       "    <button onclick=\"kotlinCommSend('t_list2', '95d0010a-7d45-488b-bdcc-9b528e11254a', {})\">Show more</button>\n",
       "    <span class=\"listContent\">[1, 2, 3, 4, 5]</span>\n",
       "</div>\n",
       "\n",
       "        <script>\n",
       "            window.kotlinCommSend = function(target, wId, command) {\n",
       "                window.kotlinComms[target][wId].send(command);\n",
       "            };\n",
       "            \n",
       "            window.setElementState = function(elem, state) {\n",
       "                \n",
       "    const contentElem = elem.getElementsByClassName('listContent')[0];\n",
       "    contentElem.innerHTML = state.d;\n",
       "\n",
       "            };\n",
       "\n",
       "            window.setState = function(target, id, data) {\n",
       "                const outs = document.getElementsByClassName(\"widget_\" + target + \"_\" + id);\n",
       "                for (out of outs) {\n",
       "                    setElementState(out, data);\n",
       "                }\n",
       "            };\n",
       "            \n",
       "            (function(){\n",
       "                const initComm = function() {\n",
       "                    const wId = \"95d0010a-7d45-488b-bdcc-9b528e11254a\";\n",
       "                    const target = \"t_list2\";\n",
       "                    const comm = Jupyter.notebook.kernel.comm_manager.new_comm(target, { id: wId });\n",
       "                    window.kotlinComms = window.kotlinComms || {};\n",
       "                    window.kotlinComms[target] = window.kotlinComms[target] || {};\n",
       "                    window.kotlinComms[target][wId] = comm;\n",
       "\n",
       "                    comm.on_msg((msg) => {\n",
       "                        const d = msg.content.data;\n",
       "                        setState(target, wId, d);\n",
       "                    });\n",
       "                };\n",
       "\n",
       "                if (Jupyter.notebook.kernel != null) initComm();\n",
       "                else window.onload = initComm;\n",
       "            })();\n",
       "        </script>"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "listWidget"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "listWidget[10] = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Kotlin",
   "language": "kotlin",
   "name": "kotlin"
  },
  "language_info": {
   "codemirror_mode": "text/x-kotlin",
   "file_extension": ".kt",
   "mimetype": "text/x-kotlin",
   "name": "kotlin",
   "nbconvert_exporter": "",
   "pygments_lexer": "kotlin",
   "version": "1.7.20-dev-1299"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
